/******************************************************************************
* Copyright (c) 2011-2013, Linagora
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Linagora - initial API and implementation
*******************************************************************************/
package com.ebmwebsourcing.petals.services.su.editor.extensibility;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.xml.namespace.QName;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.common.command.AbstractCommand;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.impl.EStructuralFeatureImpl;
import org.eclipse.emf.ecore.util.ExtendedMetaData;
import org.eclipse.emf.ecore.util.FeatureMap;
import org.eclipse.emf.ecore.util.FeatureMap.Entry;
import org.eclipse.emf.ecore.xml.type.AnyType;
import com.ebmwebsourcing.petals.common.internal.provisional.utils.JbiXmlUtils;
import com.ebmwebsourcing.petals.services.PetalsServicesPlugin;
import com.sun.java.xml.ns.jbi.AbstractExtensibleElement;
import com.sun.java.xml.ns.jbi.Provides;
/**
* @author Mickael Istria - EBM WebSourcing
* @author Vincent Zurczak - Linagora
*/
public class InitializeModelExtensionCommand extends AbstractCommand {
private static final AtomicInteger ID_COUNTER = new AtomicInteger( 1 );
private final int id;
private Set<EStructuralFeature> targetFeatures;
private final EPackage extensionPackage;
private final AbstractExtensibleElement element;
/**
* Constructor.
* @param extensionPackage
* @param element
*/
public InitializeModelExtensionCommand( EPackage extensionPackage, AbstractExtensibleElement element ) {
this.extensionPackage = extensionPackage;
this.element = element;
this.id = ID_COUNTER.getAndIncrement();
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.common.command.AbstractCommand
* #prepare()
*/
@Override
public boolean prepare() {
initializeFeatures();
return needsAdditionalAttributes();
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.common.command.Command
* #execute()
*/
@Override
public void execute() {
/* Woooooooow: some explanations are required here!
* ================================================
*
* All the features have been initialized (they were found from the extension-point and EMF registry).
* The command is executable if and only if they are elements in the EObject that can be associated with
* these structural features.
*
* That's what the command do: initialize features from the mixed content.
* Problem: if we just go through the features in the discovering order, the elements will
* be written in this same order. This is wrong because the CDK features must ALWAYS be written
* before the component one.
*
* So, before invoking this command, the extension packages must be sorted.
* The CDK package must be initialized first.
*/
// To follow insertion (and thus write) order, set debug to true.
// Set it to false before the releases. As its name says it, it is for debug purpose.
boolean debug = false;
if( debug )
System.out.println( "Command " + this.id );
// Do not insert twice the same feature - change the insertion / write order
Set<String> alreadySet = new HashSet<String> ();
// Once sorting is done, we can iterate and initialize the feature values
// Care must be taken.
for( EStructuralFeature targetFeature : this.targetFeatures ) {
List<Entry> entries = getMatchingGroupEntries( targetFeature );
if( entries.isEmpty())
continue;
String fName = ExtendedMetaData.INSTANCE.getName( targetFeature );
String fNs = ExtendedMetaData.INSTANCE.getNamespace( targetFeature );
String id = fName + " - " + fNs;
if( alreadySet.contains( id )) {
if( debug )
System.out.println( "Feature " + id + " was already set." );
continue;
}
// Analyze the entry
alreadySet.add( id );
EList<Object> eList = new BasicEList<Object> ();
for( Entry entry : entries ) {
this.element.getGroup().remove( entry );
Object value = getActualValue( entry.getValue());
Object resolvedValue = adaptValueType( value, targetFeature );
eList.add( resolvedValue );
}
Object value = targetFeature.isMany() ? eList : eList.get( 0 );
String display = "Inserting feature: " + id + " (" + value + ")";
if( targetFeature.isMany() )
display += " AS a list.";
if( debug )
System.out.println( display );
try {
this.element.eSet( targetFeature, value );
} catch( Exception e ) {
PetalsServicesPlugin.log( e, IStatus.ERROR,
"A model feature could not be initialized. Type = "
+ ((EAttribute) targetFeature).getEAttributeType().getInstanceClassName());
}
}
}
/**
* Creates the target features.
* <p>
* This methods adds all the features from the extensions in the target features set.
* </p>
* <p>
* See PETALSSTUD-245<br />
* Checks whether these features are part of the right parent (provides or consumes).<br />
* This method guarantees that we do not initialize consumes features in a provides.
* And vice-versa.
* </p>
*/
public void initializeFeatures() {
if( this.targetFeatures != null )
return;
String forbiddenWord = this.element instanceof Provides ? "consumes" : "provides";
this.targetFeatures = new LinkedHashSet<EStructuralFeature>();
if( this.extensionPackage == null )
return;
eClassLoop: for( EClassifier classifier : this.extensionPackage.getEClassifiers()) {
if( !( classifier instanceof EClass ))
continue;
// Provides or consumes: do not process them all, only one of them
EClass eClass = (EClass) classifier;
for( EClass ec : eClass.getEAllSuperTypes()) {
if( ec.getName().toLowerCase().contains( forbiddenWord )) {
continue eClassLoop;
}
}
// Add the features
for (EStructuralFeature feature : eClass.getEStructuralFeatures()) {
((EStructuralFeatureImpl) feature).setFeatureID( -1 );
this.targetFeatures.add( feature );
}
}
}
/**
* @return
*/
private boolean needsAdditionalAttributes() {
for( EStructuralFeature targetFeature : this.targetFeatures ) {
List<Entry> currentEntries = getMatchingGroupEntries( targetFeature );
if( currentEntries.isEmpty())
return true;
}
return false;
}
/**
* Adapts a raw object so that its type is valid according to the model extension.
* @param entryValue
* @param targetFeature
* @return an adapted object (null if entryValue is null)
*/
private Object adaptValueType( Object entryValue, EStructuralFeature targetFeature ) {
Object finalValue = entryValue;
if( entryValue instanceof String
&& targetFeature instanceof EAttribute ) {
EDataType expectedType = ((EAttribute) targetFeature).getEAttributeType();
String instanceClassName = expectedType.getInstanceClassName().toLowerCase();
if( expectedType.equals( EcorePackage.Literals.EINT )
|| "int".equals( instanceClassName )
|| "java.lang.integer".equals( instanceClassName )) {
finalValue = Integer.valueOf((String) entryValue);
} else if( expectedType.equals( EcorePackage.Literals.ELONG )
|| "long".equals( instanceClassName )
|| "java.lang.long".equals( instanceClassName )) {
finalValue = Long.valueOf((String) entryValue);
} else if( instanceClassName.equals( "javax.xml.namespace.qname" )) {
// Extract the QName value...
String[] parts = ((String) entryValue).split( ":" );
String ns = null, name = null;
if( parts.length == 1 ) {
name = parts[ 0 ];
} else if( parts.length == 2 ) {
ns = parts[ 0 ];
name = parts[ 1 ];
} else {
PetalsServicesPlugin.log( "Found invalid QName while intializing the model extensions.", IStatus.ERROR );
}
// ... and resolve it
QName newValue = null;
EMap<String,String> map = JbiXmlUtils.findPrefixMap( this.element );
if( map == null ) {
PetalsServicesPlugin.log( "Could not find the prefix map while intializing the model extensions.", IStatus.ERROR );
} else if( name != null ) {
if( ns != null )
ns = map.get( ns );
newValue = ns != null ? new QName( ns, name ) : new QName( name );
}
finalValue = newValue;
} else if( expectedType instanceof EEnum ) {
EEnum eEnum = (EEnum) expectedType;
EEnumLiteral literal = eEnum.getEEnumLiteralByLiteral((String) entryValue);
finalValue = literal.getInstance();
} else if( expectedType.getInstanceClass().equals( boolean.class )) {
finalValue = Boolean.valueOf((String) entryValue);
}
}
return finalValue;
}
/**
* @param value
* @return
*/
private Object getActualValue( Object value ) {
return value instanceof AnyType ? ((AnyType)value).getMixed().get(0).getValue() : value;
}
/**
* @param referenceFeature
* @return
*/
private List<Entry> getMatchingGroupEntries( EStructuralFeature referenceFeature ) {
List<Entry> result = new ArrayList<FeatureMap.Entry> ();
for (FeatureMap.Entry entry : this.element.getGroup()) {
String actualName = ExtendedMetaData.INSTANCE.getName( entry.getEStructuralFeature());
String actualNamespace = ExtendedMetaData.INSTANCE.getNamespace(entry.getEStructuralFeature());
String referenceName = ExtendedMetaData.INSTANCE.getName( referenceFeature );
String referenceNamespace = ExtendedMetaData.INSTANCE.getNamespace( referenceFeature );
boolean sameName = actualName.equals( referenceName )
|| actualName.equals( referenceFeature.getName())
|| entry.getEStructuralFeature().getName().equals( referenceName )
|| entry.getEStructuralFeature().getName().equals( referenceFeature.getName());
if( actualNamespace.equals( referenceNamespace ) && sameName )
result.add( entry );
}
return result;
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.common.command.Command
* #redo()
*/
@Override
public void redo() {
execute();
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.common.command.AbstractCommand
* #canUndo()
*/
@Override
public boolean canUndo() {
return false;
}
}